﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Multiprocessing.Core.Algorithm;
using Multiprocessing.Core.Mathematics.Matrices;
using Multiprocessing.Core.Units;

namespace Multiprocessing.Algorithms.Matrices
{
    public class MultiplyMatricesAlgorithm : BaseAlgorithm<Matrix>
    {
        #region Constructor
        
        public MultiplyMatricesAlgorithm(IProcessingBlockFactory processingBlockFactory, string name)
            : base(processingBlockFactory, name)
        {
        }

        public MultiplyMatricesAlgorithm(string name)
            : this(ProcessingBlockFactory.Instance, name)
        {
        }

        #endregion

        #region Methods
        
        public override object[] CreateParameters(int size)
        {
            Matrix first = Matrix.Factory.CreateRandom<Matrix>(size, size),
                   second = Matrix.Factory.CreateRandom<Matrix>(size, size);
            return new object[] { first, second };
        }

        public override AlgorithmResult<Matrix> Execute(IProcessingBlock processingBlock, params object[] args)
        {
            return this.Execute(processingBlock, (Matrix)args[0], (Matrix)args[1]);
        }
        
        public AlgorithmResult<Matrix> Execute(IProcessingBlock processingBlock, Matrix first, Matrix second)
        {
            if (ReferenceEquals(first, null))
            {
                throw new ArgumentNullException("first");
            }
            if (ReferenceEquals(second, null))
            {
                throw new ArgumentNullException("second");
            }
            if (ReferenceEquals(processingBlock, null))
            {
                throw new ArgumentNullException("processingBlock");
            }
            if (first.ColumnCount != second.RowCount)
            {
                throw new ArgumentException("first.ColumnCount should be equal second.RowCount");
            }

            Matrix result = Matrix.Factory.Create<Matrix>(first.RowCount, second.ColumnCount);

            int rowForUnit = first.RowCount / processingBlock.Size;
            int restRows = first.RowCount % processingBlock.Size;

            for (int unitIndex = 0, startIndexForUnit = 0; unitIndex < processingBlock.Size; ++unitIndex, startIndexForUnit += rowForUnit)
            {
                if ((unitIndex == processingBlock.Size - 1) && (restRows != 0))
                {
                    int startIndex = startIndexForUnit;
                    processingBlock[unitIndex]
                        = () =>
                              {
                                  for (int rowIndex = startIndex;
                                       rowIndex < rowForUnit + startIndex + restRows;
                                       ++rowIndex)
                                  {
                                      for (int columnIndex = 0;
                                           columnIndex < second.ColumnCount;
                                           ++columnIndex)
                                      {
                                          double value = 0.0;
                                          for (int r = 0; r < second.RowCount; ++r)
                                          {
                                              value += first[rowIndex, r]*
                                                       second[r, columnIndex];
                                          }
                                          result[rowIndex, columnIndex] = value;
                                      }
                                  }
                              };
                }
                else
                {
                    int startIndex = startIndexForUnit;
                    processingBlock[unitIndex]
                        = () =>
                              {
                                  for (int rowIndex = startIndex;
                                       rowIndex < rowForUnit + startIndex;
                                       ++rowIndex)
                                  {
                                      for (int columnIndex = 0;
                                           columnIndex < second.ColumnCount;
                                           ++columnIndex)
                                      {
                                          double value = 0.0;
                                          for (int r = 0; r < second.RowCount; ++r)
                                          {
                                              value += first[rowIndex, r]*
                                                       second[r, columnIndex];
                                          }
                                          result[rowIndex, columnIndex] = value;
                                      }
                                  }
                              };
                }
            }
            // launch cycle
            this.Stopwatch.Reset();
            this.Stopwatch.Start();
            processingBlock.StartAll();
            processingBlock.WaitAll();
            this.Stopwatch.Stop();
            return new AlgorithmResult<Matrix>(result, this.Stopwatch.Elapsed, -1.0);
        }



        #endregion

    }
}
